﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text.RegularExpressions;

namespace Freedom.Common
{
    /// <summary>
    /// Contains methods for working with <see cref="Enum"/>.
    /// </summary>
    public static class EnumHelper
    {
        private static readonly Regex CapitalLetterMatch = new Regex("\\B[A-Z]", RegexOptions.Compiled);

        /// <summary>
        /// Gets the description of.
        /// </summary>
        /// <param name="enumMember">Type of the enum.</param>
        /// <returns></returns>
        public static string GetDescription(this Enum enumMember)
        {
            var descriptionAttribute = enumMember.GetAttribute<DescriptionAttribute>();
            return (descriptionAttribute == null) ? enumMember.ToString(true) : descriptionAttribute.Description;
        }

        /// <summary>
        /// Gets the enum value by description.
        /// </summary>
        /// <typeparam name="TEnumType">The enum type.</typeparam>
        /// <param name="description">The description.</param>
        /// <returns>The enum value.</returns>
        public static TEnumType GetValueByDescription<TEnumType>(string description)
        {
            var type = typeof(TEnumType);
            if (!type.IsEnum)
                throw new ArgumentException("This method is destinated for enum types only.");
            foreach (var enumName in Enum.GetNames(type))
            {
                var enumValue = Enum.Parse(type, enumName);
                if (description == ((Enum)enumValue).GetDescription())
                    return (TEnumType)enumValue;
            }
            throw new ArgumentException("There is no value with this description among specified enum type values.");
        }

        /// <summary>
        /// Gets the acronym.
        /// </summary>
        /// <param name="enumMember">The enum member.</param>
        /// <returns></returns>
        public static string GetAcronym(this Enum enumMember)
        {
            var descriptionAttribute = enumMember.GetAttribute<AcronymAttribute>();
            return (descriptionAttribute == null) ? string.Empty : descriptionAttribute.Acronym;
        }
    

        /// <summary>
        /// Gets the attribute.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="enumMember">The enum member.</param>
        /// <returns></returns>
        public static T GetAttribute<T>(this Enum enumMember)
            where T : class
        {
            var memberInfo = enumMember.GetType().GetMember(enumMember.ToString());

            return memberInfo.Length == 0 ? null : memberInfo[0].GetAttribute<T>(false);
        }

        /// <summary>
        /// Gets the category.
        /// </summary>
        /// <param name="enumMember">The enum member.</param>
        /// <returns></returns>
        public static string GetCategory(this Enum enumMember)
        {
            var descriptionAttribute = enumMember.GetAttribute<CategoryAttribute>();
            return descriptionAttribute == null ? string.Empty : descriptionAttribute.Category;
        }

        /// <summary>
        /// Gets the categories.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns></returns>
        public static List<string> GetCategories(Type type)
        {
            var values = Enum.GetValues(type);
            var categories = new List<string>();
            foreach (Enum value in values)
            {

                var customAttributes = value.GetAttributes<CategoryAttribute>();

                foreach (var attribute in customAttributes)
                {
                    if (!categories.Contains(attribute.Category))
                        categories.Add(attribute.Category);
                }

            }
            return categories;
        }

        /// <summary>
        /// Gets the attributes.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="enumMember">The enum member.</param>
        /// <returns></returns>
        public static T[] GetAttributes<T>(this Enum enumMember) where T : class
        {
            var memberInfo = enumMember.GetType().GetMember(enumMember.ToString());

            if (memberInfo.Length == 0) return null;
            return memberInfo[0].GetCustomAttributes(false).Cast<T>().ToArray();
        }

        /// <summary>
        /// Toes the string.
        /// </summary>
        /// <param name="enumType">Type of the enum.</param>
        /// <param name="splitWords">if set to <c>true</c> [split words].</param>
        /// <returns></returns>
        public static string ToString(this Enum enumType, bool splitWords)
        {
            return splitWords ? CapitalLetterMatch.Replace(enumType.ToString(), " $&") : enumType.ToString();
        }

        /// <summary>
        /// Gets the values array.
        /// </summary>
        /// <param name="enumType">Type of the enum.</param>
        /// <returns></returns>
        public static Enum[] GetValuesArray(Type enumType)
        {
            return Enum.GetValues(enumType).Cast<Enum>().ToArray();
        }
        /// <summary>
        /// Gets the values array.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static T[] GetValuesArray<T>()
        {
            return Enum.GetValues(typeof(T)).Cast<T>().ToArray();
        }
        /// <summary>
        /// Gets the values.
        /// </summary>
        /// <param name="enumType">Type of the enum.</param>
        /// <returns></returns>
        public static IEnumerable<Enum> GetValues(Type enumType)
        {
            return Enum.GetValues(enumType).Cast<Enum>();
        }

        /// <summary>
        /// Gets the values.
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<T> GetValues<T>()
        {
            return Enum.GetValues(typeof(T)).Cast<T>();
        }
        /// <summary>
        /// Determines whether [is set flag] [the specified enum type].
        /// </summary>
        /// <param name="enumType">Type of the enum.</param>
        /// <param name="flag">The flag.</param>
        /// <returns>
        /// 	<c>true</c> if [is set flag] [the specified enum type]; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsSet(this Enum enumType, Enum flag)
        {
            var value = Convert.ToInt64(enumType);
            var flagValue = Convert.ToInt64(flag);
            return (value & flagValue) == flagValue;
        }

        /// <summary>
        /// Parses the specified value.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static T Parse<T>(string value)
        {
            return Parse<T>(value, true);
        }

        /// <summary>
        /// Parses the specified value.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value">The value.</param>
        /// <param name="ignoreCase">if set to <c>true</c> [ignore case].</param>
        /// <returns></returns>
        public static T Parse<T>(string value, bool ignoreCase)
        {
            var type = typeof(T);
            try
            {
                return (T)Enum.Parse(type, value, ignoreCase);
            }
            catch
            {
                return (T)Activator.CreateInstance(type);
            }
        }
    }
}
